# koroFileHeader at Yungoal acer
# Create: 2021-04-26 12:53:21
# LastEdit: 2021-07-09 15:04:36
"""需求：
检查VM是否有备份，检查出忘记添加备份的VM

实现：
导一份VM清单
resources
| where type == "microsoft.compute/virtualmachines"
| project id

再导一份 Recovery Services 保管库 的
recoveryservicesresources
| where type =~ "microsoft.recoveryservices/vaults/backupfabrics/protectioncontainers/protecteditems"
| where properties.protectedItemType =~ "Microsoft.Compute/virtualMachines"
| project properties.virtualMachineId, properties.friendlyName, last_recovery_point = tostring(properties.lastRecoveryPoint), properties.lastBackupTime
| order by last_recovery_point 

properties.virtualMachineId 是和上面VM的id关联的。
先输出保管库的数据，已排序，早的日期在下面，日期为空在最下面
再输出没有关联到properties.virtualMachineId的VM信息

properties.protectedItemType =~ "Microsoft.Compute/virtualMachines"，这里只要虚拟机备份，直接同类型将其他类型（比如："AzureVmWorkloadSQLDatabase"）的备份过滤掉。
"""
__author__ = '749B'

import os, sys
BASE_DIR = os.getcwd()
sys.path.append(BASE_DIR)

import argparse
import textwrap
import msrestazure.azure_cloud
# pylint: disable = import-error
from azure_client import Azure_Client
from azure_client import data_format
from utils.env import init_environs
from utils.blueking import job_start, job_success, job_fail
from utils.tools import remove_empty, id_to_dict

from typing import Optional, List
# 只是因为函数的返回需要做类型判断
from azure.mgmt.resourcegraph.models._models_py3 import QueryResponse


def get_vm(
    client: Azure_Client
    ) -> QueryResponse:
    """拉取所有的虚拟机，只要id"""
    strQuery = textwrap.dedent("""\
        resources
        | where type == "microsoft.compute/virtualmachines"
        {limit}
        | project id
        """).format(
            limit=''  # '| limit 10' for test
        )
    # 去掉空白行
    strQuery = remove_empty(strQuery)
    res = client.resource_graph_query_default(
        strQuery,
        [sub.get("subscription_id") for sub in client.get_subscriptions()],
    )
    return res

def get_backup_point(
    client: QueryResponse
    ) -> QueryResponse:
    subscriptionDisplayName = client.subscription_display_name()
    strQuery = textwrap.dedent("""\
    recoveryservicesresources
    | where type =~ "microsoft.recoveryservices/vaults/backupfabrics/protectioncontainers/protecteditems"
    | where properties.protectedItemType =~ "Microsoft.Compute/virtualMachines"
    {subscriptionDisplayName}
    {limit}
    | project properties.virtualMachineId, subscriptionDisplayName, resourceGroup, properties.friendlyName, last_recovery_point = tostring(properties.lastRecoveryPoint), properties.lastBackupTime, properties.policyName, properties.policyId
    | order by last_recovery_point 
    """).format(
        subscriptionDisplayName=subscriptionDisplayName, 
        limit='',  # `| limit 10` for test
        )
    # 去掉空白行
    strQuery = remove_empty(strQuery)
    res = client.resource_graph_query_default(
        strQuery,
        [sub.get("subscription_id") for sub in client.get_subscriptions()],
    )
    return res

def init_argparse(args: Optional[List[str]] = None) -> argparse.Namespace:
    """初始化：命令行参数"""
    parser = argparse.ArgumentParser(description='检查VM的备份还原点，包括没有添加备份的VM')
    parser.add_argument('envfile', nargs='?', help="存有用户认证信息的env文件的文件名")
    args = parser.parse_args(args)
    return args    

def main(args: Optional[List[str]] = None):
    """主函数
    查询、分析、打印
    """
    # 初始化命令行参数
    if args is None:
        # 蓝鲸作业平台有BUG，重做的任务，空着的参数里都会被填充null
        args = [arg for arg in sys.argv[1:] if arg != 'null' and not arg.endswith('=null')]
    args = init_argparse(args)
    if not args.envfile:
        return job_fail("请提供存有用户认证信息的env文件的文件名")

    # 初始化环境变量
    envs = init_environs(args.envfile)
    if not envs:
        return job_fail("env文件解密错误")
    if envs['runtime_env'] == 'blueking':
        for key in args.__dict__.keys():
            if key.startswith('dev_'):
                v = getattr(args, key)
                if v:
                    print(f'{key}={v}: 不支持的命令行参数，已经忽略。', file=sys.stderr)
                    setattr(args, key, None)

    # 实例化客户端
    CLOUD = getattr(msrestazure.azure_cloud, envs['cloud_name'])
    client = Azure_Client(CLOUD)

    res_vm = get_vm(client)
    # 所有虚拟机的id列表
    vm_id_list = [i.get('id').lower() for i in res_vm.data]
    
    res_backup_point = get_backup_point(client)
    # UTC转为本地时间
    data_format.parse_time(res_backup_point.data)
    # RecoveryVaultName 的值需要提取
    data_format.recovery_vault_name(res_backup_point.data)
    data_format.output_csv(
        res_backup_point.data, 
        column_map={'subscriptionDisplayName': 'Sub'},
        ignores=['properties_virtualMachineId'],
        )
    
    # 检查没有备份还原点的vm，从vm_id_list中移除有备份还原点的vmid，剩下的就是。
    for row in res_backup_point.data:
        vmid = row.get('properties_virtualMachineId').lower()
        if vmid in vm_id_list:
            vm_id_list.remove(vmid)
        else:
            # 备份还原点里有记录虚拟机，通过properties_virtualMachineId无法在VM中找到虚拟机
            # 应该是账号没有对应虚拟机的查看权限
            # 忽略这种情况
            pass

    # pylint: disable = no-member
    sub_map = {sub.get("subscription_id"): sub.get("display_name") for sub in client.get_subscriptions()}
    for vmid in vm_id_list:
        vminfo = id_to_dict(vmid)
        line = [
            sub_map.get(vminfo.get('subscriptions'), vminfo.get('subscriptions')), 
            vminfo.get('resourcegroups'), 
            vminfo.get('virtualmachines'),
            '', '', '', '',
            ]
        print(','.join(line))


if __name__ == '__main__':

    job_start()

    ###### 可在此处开始编写您的脚本逻辑代码
    ###### iJobs中执行脚本成功和失败的标准只取决于脚本最后一条执行语句的返回值
    ###### 如果返回值为0，则认为此脚本执行成功，如果非0，则认为脚本执行失败

    main()


